/*
 * Copyright 2011 Dominik Pretzsch <dominik.pretzsch at gmail.com>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.blacksheep.jmoteextract.archive;

import de.blacksheep.jmoteextract.remote.IRemoteObservable;
import java.util.HashMap;
import java.util.Observable;
import de.blacksheep.jmoteextract.remote.IRemoteObserver;
import de.blacksheep.jmoteextract.remote.RemoteArchive;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Dominik Pretzsch <dominik.pretzsch at gmail.com>
 */
public class ArchiveStorage extends Observable {

    private static ArchiveStorage instance;
    private static HashMap<Integer, Archive> archiveMap;
    
    private static final Object sync   = new Object();
    private static final Logger LOGGER =  Logger.getLogger(ArchiveStorage.class.getName());
    
    private ArchiveStorage() {
        archiveMap = new HashMap<Integer, Archive>();
    }
    
    public static ArchiveStorage getInstance() {
        if (instance == null) {
            instance = new ArchiveStorage();
        }
        return instance;
    }
    
    private int add(Archive archive) {
        LOGGER.log(Level.FINE, "Try to add(archive)...");
        
        int hash = archive.getHash();
        if (hash == 0) {
            hash = archive.hashCode();
            archive.setHash(hash);
        }
        
        synchronized (sync) {
            archiveMap.put(hash, archive);
        }
        
        LOGGER.log(Level.FINE, "Added Archive. Hash: {0}", hash);
        
        setChanged();
        notifyObservers(hash);
        
        return hash;
    }
    
    public int createLocalArchive(RemoteArchive ra) {
        LOGGER.log(Level.FINE, "Try to createLocalArchive");
        
        Archive archive = new Archive(ra.getNameUNC());
        archive.setAbsoluteExtractionPath(determineExtractionPath(archive));
        archive.makeObservable();
        
        return add(archive);
    }
    
    public int createNonLocalArchive(int hash, RemoteArchive ra) {
        LOGGER.log(Level.FINE, "Try to createNonLocalArchive {0}...", hash);
        
        Archive archive = new Archive(hash, ra.getNameUNC());
        archive.setAbsoluteExtractionPath(determineExtractionPath(archive));
        archive.setIsNonLocalArchive(true);
        
        return add(archive);
    }
    
    public Archive get(int hash) {
        LOGGER.log(Level.FINE, "Try to get {0}", hash);
        Archive archive = null;
        synchronized (sync) {
            archive = archiveMap.get(hash);
        }
        return archive;
    }
    
    public String getAbsolutePath(int hash) {
        Archive archive = null;
        synchronized (sync) {
            archive = archiveMap.get(hash);
        }
        return archive.getAbsolutePath();
    }
    
    public String getAbsoluteExtractionPath(int hash) {
        Archive archive = null;
        synchronized (sync) {
            archive = archiveMap.get(hash);
        }
        return archive.getAbsoluteExtractionPath();
    }
    
    public boolean getIsNonLocalArchive(int hash) {
        Archive archive = null;
        synchronized (sync) {
            archive = archiveMap.get(hash);
        }
        return archive.isNonLocalArchive();
    }
    
    public void remove(int hash) {
        LOGGER.log(Level.FINE, "Try to remove {0}...", hash);
        synchronized (sync) {
            archiveMap.remove(hash);
        }
        LOGGER.log(Level.FINE, "Removed ArchiveBundle {0}", hash);
    }
    
    private String determineExtractionPath(Archive archive) {
        // TODO: Get extraction path from SPOT (ra? server? config?)
        String extPath = archive.getAbsolutePath();
        extPath = extPath.substring(0, extPath.lastIndexOf("\\"));
        extPath = extPath.concat("\\").concat(archive.getFileName());
        extPath = extPath.substring(0, extPath.lastIndexOf("."));
        
        return extPath;
    }
    
    public void assignObserver(int hash, IRemoteObserver ro) {
        LOGGER.log(Level.FINE, "Try to assign an Observer for {0}...", hash);
        Archive archive = null;
        synchronized (sync) {
            archive = archiveMap.get(hash);
        }       
        if (archive != null  
                && !archive.isNonLocalArchive()  
                &&  archive.isObservable() 
                && !archive.hasObserver()) {
            
            archive.assignObserver(ro);
            archive.addObserverToObservable();
            LOGGER.log(Level.FINE, "Observer assigned for {0}", hash);
        }
    }
    
    /*
     * Caches the RemoteObserver object in the local storage,
     * so it won't be GC'ed.
     */
    public void cacheObserver(int hash, IRemoteObserver ro) {        
        LOGGER.log(Level.FINE, "Try to cacheObserver for {0}...", hash);
        Archive archive = null;
        synchronized (sync) {
            archive = archiveMap.get(hash);
        }
        if (archive != null 
                &&  archive.isNonLocalArchive() 
                && !archive.hasObserver()) {
            
            archive.assignObserver(ro);
            LOGGER.log(Level.FINE, "Observer cached for {0}", hash);
        }
    }
    
    public IRemoteObserver getObserver(int hash) {
        Archive archive = null;
        synchronized (sync) {
            archive = archiveMap.get(hash);
        }
        return archive.getObserver();
    }
    
    public IRemoteObservable getObservable(int hash) {
        Archive archive = null;
        synchronized (sync) {
            archive = archiveMap.get(hash);
        }
        return archive.getObservable();
    }
}
